home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 8: LINUX Games
/
Linux Cubed Series 8 - LINUX Games.iso
/
games
/
muds
/
lpmud312.tar
/
lpmud312
/
backend.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-01-11
|
23KB
|
939 lines
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/times.h>
#include <math.h>
#include <memory.h>
#include "lint.h"
#include "config.h"
#include "interpret.h"
#include "object.h"
#include "wiz_list.h"
#include "exec.h"
#include "comm.h"
jmp_buf error_recovery_context;
int error_recovery_context_exists = 0;
/*
* The 'current_time' is updated at every heart beat.
*/
int current_time;
static void cycle_hb_list PROT((void));
extern struct object *command_giver, *current_interactive, *obj_list_destruct;
extern int num_player, d_flag;
extern struct object *previous_ob, *master_ob;
struct object *current_heart_beat;
void call_heart_beat(), catch_alarm();
void load_first_objects(), prepare_ipc(),
shutdowngame(), ed_cmd PROT((char *)),
print_prompt(), call_out(),
destruct2 PROT((struct object *));
extern int get_message PROT((char *, int)), player_parser PROT((char *)),
call_function_interactive PROT((struct interactive *, char *)),
resort_free_list(), swap PROT((struct object *));
extern void flush_all_player_mess();
extern int t_flag;
int time_to_call_heart_beat;
int comm_time_to_call_heart_beat = 0; /* this is set by interrupt, */
/* comm sets time_to_call_heart_beat sometime after */
/*
* There are global variables that must be zeroed before any execution.
* In case of errors, there will be a longjmp(), and the variables will
* have to be cleared explicitely. They are normally maintained by the
* code that use them.
*
* This routine must only be called from top level, not from inside
* stack machine execution (as stack will be cleared).
*/
void clear_state() {
extern struct object *previous_ob;
current_object = 0;
command_giver = 0;
current_interactive = 0;
previous_ob = 0;
current_prog = 0;
error_recovery_context_exists = 1;
reset_machine(0); /* Pop down the stack. */
}
void logon(ob)
struct object *ob;
{
struct svalue *ret;
struct object *save = current_object;
/*
* current_object must be set here, so that the static "logon" in
* player.c can be called.
*/
current_object = ob;
ret = apply("logon", ob, 0);
if (ret == 0) {
add_message("prog %s:\n", ob->name);
fatal("Could not find logon on the player %s\n", ob->name);
}
current_object = save;
}
/*
* Take a player command and parse it.
* The command can also come from a NPC.
* Beware that 'str' can be modified and extended !
*/
int parse_command(str, ob)
char *str;
struct object *ob;
{
struct object *save = command_giver;
int res;
command_giver = ob;
res = player_parser(str);
command_giver = save;
return res;
}
/*
* This is the backend. We will stay here for ever (almost).
*/
int eval_cost;
void backend()
{
char buff[2000];
extern int game_is_being_shut_down;
extern int slow_shut_down_to_do;
(void)printf("Setting up ipc.\n");
fflush(stdout);
prepare_ipc();
(void)signal(SIGHUP, startshutdowngame);
if (!t_flag)
call_heart_beat();
setjmp(error_recovery_context);
while(1) {
/*
* The call of clear_state() should not really have to be done
* once every loop. However, there seem to be holes where the
* state is not consistent. If these holes are removed,
* then the call of clear_state() can be moved to just before the
* while() - statment. *sigh* /Lars
*/
clear_state();
eval_cost = 0;
remove_destructed_objects(); /* marion - before ref checks! */
#ifdef DEBUG
if (d_flag > 1)
check_a_lot_ref_counts(0);
#endif
if (game_is_being_shut_down)
shutdowngame();
if (slow_shut_down_to_do) {
int tmp = slow_shut_down_to_do;
slow_shut_down_to_do = 0;
slow_shut_down(tmp);
}
if (get_message(buff, sizeof buff)) {
void update_load_av PROT((void));
update_load_av();
/*
* Now we have a string from the player. This string can go to
* one of several places. If it is prepended with a '!', then
* it is an escape from the 'ed' editor, so we send it
* as a command to the parser.
* If any object function is waiting for an input string, then
* send it there.
* Otherwise, send the string to the parser.
* The player_parser() will find that current_object is 0, and
* then set current_object to point to the object that defines
* the command. This will enable such functions to be static.
*/
current_object = 0;
current_interactive = command_giver;
#ifdef DEBUG
if (!command_giver->interactive)
fatal("Non interactive player in main loop !\n");
#endif
if (buff[0] == '!' && command_giver->super)
parse_command(buff+1, command_giver);
else if (command_giver->interactive->ed_buffer)
ed_cmd(buff);
else if (call_function_interactive(command_giver->interactive,buff))
; /* Do nothing ! */
else
parse_command(buff, command_giver);
/*
* Print a prompt if player is still here.
*/
if (command_giver->interactive)
print_prompt();
}
if (time_to_call_heart_beat)
call_heart_beat();
command_giver = 0;
}
}
/*
* Despite the name, this routine takes care of several things.
* It will loop through all objects once every 10 minutes.
*
* If an object is found in a state of not having done reset, and the
* delay to next reset has passed, then reset() will be done.
*
* If the object has a existed more than the time limit given for swapping,
* then 'clean_up' will first be called in the object, after which it will
* be swapped out if it still exists.
*
* There are some problems if the object self-destructs in clean_up, so
* special care has to be taken of how the linked list is used.
*/
static void look_for_objects_to_swap() {
extern long time_to_swap; /* marion - for invocation parameter */
static int next_time;
struct object *ob;
struct object *next_ob;
jmp_buf save_error_recovery_context;
int save_rec_exists;
if (current_time < next_time)
return; /* Not time to look yet */
next_time = current_time + 15 * 60; /* Next time is in 15 minutes */
memcpy((char *) save_error_recovery_context,
(char *) error_recovery_context, sizeof error_recovery_context);
save_rec_exists = error_recovery_context_exists;
/*
* Objects object can be destructed, which means that
* next object to investigate is saved in next_ob. If very unlucky,
* that object can be destructed too. In that case, the loop is simply
* restarted.
*/
for (ob = obj_list; ob; ob = next_ob) {
int ready_for_swap;
if (ob->flags & O_DESTRUCTED) {
ob = obj_list; /* restart */
}
next_ob = ob->next_all;
if (setjmp(error_recovery_context)) { /* amylaar */
extern void clear_state();
clear_state();
debug_message("Error in look_for_objects_to_swap.\n");
continue;
}
/*
* Check reference time before reset() is called.
*/
if (current_time < ob->time_of_ref + time_to_swap)
ready_for_swap = 0;
else
ready_for_swap = 1;
/*
* Should this object have reset(1) called ?
*/
if (ob->next_reset < current_time && !(ob->flags & O_RESET_STATE)) {
if (d_flag)
fprintf(stderr, "RESET %s\n", ob->name);
reset_object(ob, 1);
}
#if TIME_TO_CLEAN_UP > 0
/*
* Has enough time passed, to give the object a chance
* to self-destruct ? Save the O_RESET_STATE, which will be cleared.
*
* Only call clean_up in objects that has defined such a function.
*
* Only if the clean_up returns a non-zero value, will it be called
* again.
*/
if (current_time - ob->time_of_ref > TIME_TO_CLEAN_UP &&
(ob->flags & O_WILL_CLEAN_UP))
{
int save_reset_state = ob->flags & O_RESET_STATE;
struct svalue *svp;
if (d_flag)
fprintf(stderr, "clean up %s\n", ob->name);
/*
* Supply a flag to the object that says if this program
* is inherited by other objects. Cloned objects might as well
* believe they are not inherited. Swapped objects will not
* have a ref count > 1 (and will have an invalid ob->prog
* pointer).
*/
push_number(ob->flags & (O_CLONE|O_SWAPPED) ? 0 : ob->prog->ref);
svp = apply("clean_up", ob, 1);
if (ob->flags & O_DESTRUCTED)
continue;
if (!svp || (svp->type == T_NUMBER && svp->u.number == 0))
ob->flags &= ~O_WILL_CLEAN_UP;
ob->flags |= save_reset_state;
}
#endif /* TIME_TO_CLEAN_UP > 0 */
#if TIME_TO_SWAP > 0
/*
* At last, there is a possibility that the object can be swapped
* out.
*/
if (ob->flags & O_SWAPPED || !ready_for_swap)
continue;
if (ob->flags & O_HEART_BEAT)
continue;
if (d_flag)
fprintf(stderr, "swap %s\n", ob->name);
swap(ob); /* See if it is possible to swap out to disk */
#endif
}
memcpy((char *) error_recovery_context,
(char *) save_error_recovery_context,
sizeof error_recovery_context);
error_recovery_context_exists = save_rec_exists;
}
/*
* Call all heart_beat() functions in all objects. Also call the next reset,
* and the call out.
* We do heart beats by moving each object done to the end of the heart beat
* list before we call its function, and always using the item at the head
* of the list as our function to call. We keep calling heart beats until
* a timeout or we have done num_heart_objs calls. It is done this way so
* that objects can delete heart beating objects from the list from within
* their heart beat without truncating the current round of heart beats.
*
* Set command_giver to current_object if it is a living object. If the object
* is shadowed, check the shadowed object if living. There is no need to save
* the value of the command_giver, as the caller resets it to 0 anyway.
*/
static struct object * hb_list = 0; /* head */
static struct object * hb_tail = 0; /* for sane wrap around */
static int num_hb_objs = 0; /* so we know when to stop! */
static int num_hb_calls = 0; /* stats */
static float perc_hb_probes = 100.0; /* decaying avge of how many complete */
void call_heart_beat() {
struct object *ob, *hide_current = current_object;
int num_done = 0;
time_to_call_heart_beat = 0; /* interrupt loop if we take too long */
comm_time_to_call_heart_beat = 0;
#ifndef MSDOS
(void)signal(SIGALRM, catch_alarm);
alarm(2);
#else
start_timer(2);
#endif
current_time = get_current_time();
current_interactive = 0;
if ((num_player > 0) && hb_list) {
num_hb_calls++;
while (hb_list &&
#ifndef MSDOS
!comm_time_to_call_heart_beat
#else
!timer_expired()
#endif
&& (num_done < num_hb_objs)) {
num_done++;
cycle_hb_list();
ob = hb_tail; /* now at end */
if (!(ob->flags & O_HEART_BEAT))
fatal("Heart beat not set in object on heart beat list!");
if (ob->flags & O_SWAPPED)
fatal("Heart beat in swapped object.\n");
/* move ob to end of list, do ob */
if (ob->prog->heart_beat == -1)
continue;
current_prog = ob->prog;
current_object = ob;
current_heart_beat = ob;
command_giver = ob;
while(command_giver->shadowing)
command_giver = command_giver->shadowing;
if (!(command_giver->flags & O_ENABLE_COMMANDS))
command_giver = 0;
if (ob->user)
ob->user->heart_beats++;
eval_cost = 0;
call_function(ob->prog,
&ob->prog->functions[ob->prog->heart_beat]);
}
if (num_hb_objs)
perc_hb_probes = 100 * (float) num_done / num_hb_objs;
else
perc_hb_probes = 100.0;
}
current_object = hide_current;
current_heart_beat = 0;
look_for_objects_to_swap();
call_out(); /* some things depend on this, even without players! */
flush_all_player_mess();
wiz_decay();
#ifdef MUDWHO
sendmudwhoinfo();
#endif
}
/*
* Take the first object off the heart beat list, place it at the end
*/
static void cycle_hb_list()
{
struct object * ob;
if (!hb_list)
fatal("Cycle heart beat list with empty list!");
if (hb_list == hb_tail)
return; /* 1 object on list */
ob = hb_list;
hb_list = hb_list -> next_heart_beat;
hb_tail -> next_heart_beat = ob;
hb_tail = ob;
ob->next_heart_beat = 0;
}
/*
* add or remove an object from the heart beat list; does the major check...
* If an object removes something from the list from within a heart beat,
* various pointers in call_heart_beat could be stuffed, so we must
* check current_heart_beat and adjust pointers.
*/
int set_heart_beat(ob, to)
struct object * ob;
int to;
{
struct object * o = hb_list;
struct object * oprev = 0;
if (ob->flags & O_DESTRUCTED)
return 0;
if (to)
to = 1;
while (o && o != ob) {
if (!(o->flags & O_HEART_BEAT))
fatal("Found disabled object in the active heart beat list!\n");
oprev = o;
o = o->next_heart_beat;
}
if (!o && (ob->flags & O_HEART_BEAT))
fatal("Couldn't find enabled object in heart beat list!");
if (to == ((ob->flags & O_HEART_BEAT) != 0))
return(0);
if (to) {
ob->flags |= O_HEART_BEAT;
if (ob->next_heart_beat)
fatal("Dangling pointer to next_heart_beat in object!");
ob->next_heart_beat = hb_list;
hb_list = ob;
if (!hb_tail) hb_tail = ob;
num_hb_objs++;
cycle_hb_list(); /* Added by Linus. 911104 */
}
else { /* remove all refs */
ob->flags &= ~O_HEART_BEAT;
if (hb_list == ob)
hb_list = ob->next_heart_beat;
if (hb_tail == ob)
hb_tail = oprev;
if (oprev)
oprev->next_heart_beat = ob->next_heart_beat;
ob->next_heart_beat = 0;
num_hb_objs--;
}
return(1);
}
/*
* sigh. Another status function.
*/
int heart_beat_status(verbose)
int verbose;
{
char buf[20];
if (verbose) {
add_message("\nHeart beat information:\n");
add_message("-----------------------\n");
add_message("Number of objects with heart beat: %d, starts: %d\n",
num_hb_objs, num_hb_calls);
sprintf(buf, "%.2f", perc_hb_probes);
add_message("Percentage of HB calls completed last time: %s\n", buf);
}
return 0;
}
/*
* There is a file with a list of objects to be initialized at
* start up.
*/
void load_first_objects() { /* Old version used when o_flag true /JnA */
FILE *f;
char buff[1000];
char *p;
extern int e_flag;
#ifndef MSDOS
struct tms tms1, tms2;
#else
long timer;
#endif
if (e_flag)
return;
(void)printf("Loading init file %s\n", INIT_FILE);
f = fopen(INIT_FILE, "r");
if (f == 0)
return;
if (setjmp(error_recovery_context)) {
clear_state();
add_message("Anomaly in the fabric of world space.\n");
}
error_recovery_context_exists = 1;
#ifndef MSDOS
times(&tms1);
#else
timer = 0L;
(void) milliseconds(&timer);
#endif
while(1) {
if (fgets(buff, sizeof buff, f) == NULL)
break;
if (buff[0] == '#')
continue;
p = strchr(buff, '\n');
if (p != 0)
*p = 0;
if (buff[0] == '\0')
continue;
(void)printf("Preloading: %s", buff);
fflush(stdout);
eval_cost = 0;
(void)find_object(buff);
#ifdef MALLOC_malloc
resort_free_list();
#endif
#ifndef MSDOS
times(&tms2);
(void)printf(" %.2f\n", (tms2.tms_utime - tms1.tms_utime +
tms2.tms_stime - tms1.tms_stime) / 60.0);
tms1 = tms2;
#else
(void)printf(" %.2f\n", milliseconds(&timer)/1000.0);
#endif
fflush(stdout);
}
error_recovery_context_exists = 0;
fclose(f);
}
/*
* New version used when not in -o mode. The epilog() in master.c is
* supposed to return an array of files (castles in 2.4.5) to load. The array
* returned by apply() will be freed at next call of apply(), which means that
* the ref count has to be incremented to protect against deallocation.
*
* The master object is asked to do the actual loading.
*/
void preload_objects(eflag)
int eflag;
{
struct vector *prefiles;
struct svalue *ret;
int ix;
push_number(eflag);
ret = apply_master_ob("epilog", 1);
if ((ret == 0) || (ret->type != T_POINTER))
return;
else
prefiles = ret->u.vec;
if ((prefiles == 0) || (prefiles->size < 1))
return;
prefiles->ref++;
ix = -1;
if (setjmp(error_recovery_context)) {
clear_state();
add_message("Anomaly in the fabric of world space.\n");
}
error_recovery_context_exists = 1;
while (++ix < prefiles->size) {
if (prefiles->item[ix].type != T_STRING)
continue;
eval_cost = 0;
push_string(prefiles->item[ix].u.string, STRING_MALLOC);
(void)apply_master_ob("preload", 1);
#ifdef MALLOC_malloc
resort_free_list();
#endif
}
free_vector(prefiles);
error_recovery_context_exists = 0;
}
/*
* catch alarm, set flag for comms code and heart_beat to catch.
* comms code sets time_to_call_heart_beat for the backend when
* it has completed the current round of player commands.
*/
void catch_alarm() {
comm_time_to_call_heart_beat = 1;
}
/*
* All destructed objects are moved int a sperate linked list,
* and deallocated after program execution.
*/
void remove_destructed_objects()
{
struct object *ob, *next;
for (ob=obj_list_destruct; ob; ob = next) {
next = ob->next_all;
destruct2(ob);
}
obj_list_destruct = 0;
}
/*
* Append string to file. Return 0 for failure, otherwise 1.
*/
int write_file(file, str)
char *file;
char *str;
{
FILE *f;
#ifdef COMPAT_MODE
file = check_file_name(file, 1);
#else
file = check_valid_path(file, current_object->eff_user, "write_file", 1);
#endif
if (!file)
return 0;
f = fopen(file, "a");
if (f == 0)
error("Wrong permissions for opening file %s for append.\n", file);
fwrite(str, strlen(str), 1, f);
fclose(f);
return 1;
}
char *read_file(file,start,len)
char *file;
int start,len;
{
struct stat st;
FILE *f;
char *str,*p,*p2,*end,c;
int size;
if (len < 0) return 0;
#ifdef COMPAT_MODE
file = check_file_name(file, 0);
#else
file = check_valid_path(file, current_object->eff_user, "read_file", 0);
#endif
if (!file)
return 0;
f = fopen(file, "r");
if (f == 0)
return 0;
if (fstat(fileno(f), &st) == -1)
fatal("Could not stat an open file.\n");
size = st.st_size;
if (size > READ_FILE_MAX_SIZE) {
if ( start || len ) size = READ_FILE_MAX_SIZE;
else {
fclose(f);
return 0;
}
}
if (!start) start = 1;
if (!len) len = READ_FILE_MAX_SIZE;
str = xalloc(size + 1);
str[size] = '\0';
do {
if (size > st.st_size)
size = st.st_size;
if (fread(str, size, 1, f) != 1) {
fclose(f);
free(str);
return 0;
}
st.st_size -= size;
end = str+size;
for (p=str; ( p2=memchr(p,'\n',end-p) ) && --start; ) p=p2+1;
} while ( start > 1 );
for (p2=str; p != end; ) {
c = *p++;
if ( !isprint(c) && !isspace(c) ) c=' ';
*p2++=c;
if ( c == '\n' )
if (!--len) break;
}
if ( len && st.st_size ) {
size -= ( p2-str) ;
if (size > st.st_size)
size = st.st_size;
if (fread(p2, size, 1, f) != 1) {
fclose(f);
free(str);
return 0;
}
st.st_size -= size;
end = p2+size;
for (; p2 != end; ) {
c = *p2;
if ( !isprint(c) && !isspace(c) ) *p2=' ';
p2++;
if ( c == '\n' )
if (!--len) break;
}
if ( st.st_size && len ) {
/* tried to read more than READ_MAX_FILE_SIZE */
fclose(f);
free(str);
return 0;
}
}
*p2='\0';
fclose(f);
#if 0 /* caller immediately frees the string again,
* so there's no use to make it smaller now
*/
if ( st.st_size > (p2-str) ) {
/* can't allocate shared string when string type isn't passed to the caller */
p2=strdup(str);
free(str);
return p2;
}
#endif
return str;
}
char *read_bytes(file,start,len)
char *file;
int start,len;
{
struct stat st;
char *str,*p;
int size, f;
int lseek();
if (len < 0)
return 0;
if(len > MAX_BYTE_TRANSFER)
return 0;
#ifdef COMPAT_MODE
file = check_file_name(file, 0);
#else
file = check_valid_path(file, current_object->eff_user,
"read_bytes", 0);
#endif
if (!file)
return 0;
f = open(file, O_RDONLY);
if (f < 0)
return 0;
if (fstat(f, &st) == -1)
fatal("Could not stat an open file.\n");
size = st.st_size;
if(start < 0)
start = size + start;
if (start >= size) {
close(f);
return 0;
}
if ((start+len) > size)
len = (size - start);
if ((size = lseek(f,start, 0)) < 0)
return 0;
str = xalloc(len + 1);
size = read(f, str, len);
close(f);
if (size <= 0) {
free(str);
return 0;
}
/* We want to allow all characters to pass untouched!
for (il=0;il<size;il++)
if (!isprint(str[il]) && !isspace(str[il]))
str[il] = ' ';
str[il] = 0;
*/
/*
* The string has to end to '\0'!!!
*/
str[size] = '\0';
p = string_copy(str);
free(str);
return p;
}
int write_bytes(file,start,str)
char *file, *str;
int start;
{
struct stat st;
int size, f;
int lseek();
#ifdef COMPAT_MODE
file = check_file_name(file, 1);
#else
file = check_valid_path(file, current_object->eff_user,
"write_bytes", 1);
#endif
if (!file)
return 0;
if(strlen(str) > MAX_BYTE_TRANSFER)
return 0;
f = open(file, O_WRONLY);
if (f < 0)
return 0;
if (fstat(f, &st) == -1)
fatal("Could not stat an open file.\n");
size = st.st_size;
if(start < 0)
start = size + start;
if (start >= size) {
close(f);
return 0;
}
if ((start+strlen(str)) > size)
return 0;
if ((size = lseek(f,start, 0)) < 0)
return 0;
size = write(f, str, strlen(str));
close(f);
if (size <= 0) {
return 0;
}
return 1;
}
int file_size(file)
char *file;
{
struct stat st;
#ifdef COMPAT_MODE
file = check_file_name(file, 0);
#else
file = check_valid_path(file, current_object->eff_user, "file_size", 0);
#endif
if (!file)
return -1;
if (stat(file, &st) == -1)
return -1;
if (S_IFDIR & st.st_mode)
return -2;
return st.st_size;
}
static double load_av = 0.0;
void update_load_av() {
extern double consts[5];
extern int current_time;
static int last_time;
int n;
double c;
static int acc = 0;
acc++;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < sizeof consts / sizeof consts[0])
c = consts[n];
else
c = exp(- n / 900.0);
load_av = c * load_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
static double compile_av = 0.0;
void update_compile_av(lines)
int lines;
{
extern double consts[5];
extern int current_time;
static int last_time;
int n;
double c;
static int acc = 0;
acc += lines;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < sizeof consts / sizeof consts[0])
c = consts[n];
else
c = exp(- n / 900.0);
compile_av = c * compile_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
char *query_load_av() {
static char buff[100];
sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
return buff;
}